package com.xiao.gulimall.product.service.impl;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.nacos.client.utils.StringUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.xiao.common.utils.PageUtils;
import com.xiao.common.utils.Query;

import com.xiao.common.utils.R;
import com.xiao.gulimall.product.dao.CategoryDao;
import com.xiao.gulimall.product.entity.CategoryEntity;
import com.xiao.gulimall.product.service.CategoryService;
import com.xiao.gulimall.product.vo.Catalog2Vo;
import com.xiao.gulimall.product.vo.Catalog3Vo;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    RedissonClient redissonClient;


    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    //分布式锁
    @Override
    public Map<String, List<Catalog2Vo>> catalogRedis() {
        //是否存在缓存
        String catelist = stringRedisTemplate.opsForValue().get("catelist");
        if(catelist!=null){
            //redis中有数据
            Map<String, List<Catalog2Vo>> longListMap = JSON.parseObject(catelist, new TypeReference<Map<String, List<Catalog2Vo>>>() {
            });
            System.out.println("缓存中数据---------------->");
            return longListMap;
        }else{
            //占坑
            ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
            String uuid = UUID.randomUUID().toString();
            Boolean lock = ops.setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS);
            //占锁成功
            if(lock){
                System.out.println("获取锁成功!");
                try {
                    //redis中没有数据
                    Map<String, List<Catalog2Vo>> stringListMap = cateloglist();
                    String string = JSON.toJSONString(stringListMap);
                    System.out.println("查数据库+++++++++++++++++>");
                    if (string != null || string.equals("")) {
                        //缓存穿透  如果string 不存在 查询缓存
                        stringRedisTemplate.opsForValue().set("catelist", string, 30, TimeUnit.SECONDS);
                    }
                    //通过随机数来防止 缓存雪崩
                    Random random = new Random();
                    int i = random.nextInt(300);
                    stringRedisTemplate.opsForValue().set("catelist", string, 3 + i, TimeUnit.SECONDS);
                    return stringListMap;
                }finally {
                    // 删除锁原子性 删除锁对比uuid 防止刚查到uuid还没有来的及对比,锁过期，对比成功删除锁，删别的锁
                    //保证删锁原子性 使用lua脚本
                    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                    //删除锁
                    stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
                }
            }else{
                //占锁失败
                System.out.println("获取失败!");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return catalogRedis();
            }
        }
    }

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public R treelist() {
        return null;
    }

    @Override
    public HashMap<String, List<Catalog2Vo>> cateloglistjichaun() {
        String rediscateloglist = redisTemplate.opsForValue().get("rediscateloglist");
        if(rediscateloglist!=null){
            HashMap<String, List<Catalog2Vo>> listHashMap = JSON.parseObject(rediscateloglist, new TypeReference<HashMap<String, List<Catalog2Vo>>>() {
            });
            System.out.println("从缓存中获取");
            return listHashMap;
        }else{
            //去redis缓存中占锁 (如果三个人去访问数据库  只可能有一个人占锁成功  其余二人进行线程等待)
            String uuid = UUID.randomUUID().toString();
            ValueOperations<String, String> operations = redisTemplate.opsForValue();
            //如果为空，就set值 并返回1 如果存在（不为空）不进行操作，并返回0
            Boolean lock = operations.setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS);
            if(lock){
                //访问成功
                System.out.println("获取锁成功");
                HashMap<String, List<Catalog2Vo>> dbmap = cateloglist();
                String s = JSON.toJSONString(dbmap);
                //解决了穿透
                if(s==null || StringUtils.isEmpty(s)){
                    redisTemplate.opsForValue().set("rediscateloglist",s,30,TimeUnit.SECONDS);
                }
                //解决了雪崩
                Random random = new Random();
                int i = random.nextInt(300);

                redisTemplate.opsForValue().set("rediscateloglist",s,300+i,TimeUnit.SECONDS);
                return dbmap;
            }else{
                //没有成功获取锁
                System.out.println("获取分布式锁 失败,等待中~~~~~~~~~~");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //等待100秒后重新访问
                return cateloglistjichaun();
            }
        }
    }

    //redisson读写锁
    @Override
    public HashMap<String, List<Catalog2Vo>> catelogredisson() {
        //获取读写锁
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("redisreadwriterclock");
        //读锁
        RLock rLock = readWriteLock.readLock();
        //写锁
        RLock writeLock = readWriteLock.writeLock();
        String rediscateloglist=null;
        //加读锁
        rLock.lock(10,TimeUnit.SECONDS);
        //try和finally（finally是无论try执行什么都会执行finally模块里的东西）
        try {
            System.out.println("读锁加锁成功"+Thread.currentThread().getId());
            //进行去redis中查询
            rediscateloglist = redisTemplate.opsForValue().get("rediscateloglist");
        }finally {
            //释放锁
            rLock.unlock();
        }
        if(rediscateloglist!=null){
            //进入缓存  转为object对象类型
            HashMap<String, List<Catalog2Vo>> listHashMap = JSON.parseObject(rediscateloglist, new TypeReference<HashMap<String, List<Catalog2Vo>>>() {
            });
            return listHashMap;
        }else{
            //进入数据库查询 开启写锁
            writeLock.lock(30,TimeUnit.SECONDS);
            try {
                //再次进入缓存查询
                rediscateloglist = redisTemplate.opsForValue().get("rediscateloglist");
                if(rediscateloglist!=null){
                    //转换为对象
                    HashMap<String, List<Catalog2Vo>> parseObject = JSON.parseObject(rediscateloglist, new TypeReference<HashMap<String, List<Catalog2Vo>>>() {
                    });
                    return parseObject;
                }else{
                    HashMap<String, List<Catalog2Vo>> cateloglist = cateloglist();
                    //先进行类型转换为json  (因为redis中只能存放json类型)
                    String s = JSON.toJSONString(cateloglist);
                    //1.穿透
                    if(s==null || StringUtils.isEmpty(s)){
                        redisTemplate.opsForValue().set("rediscateloglist",s,20,TimeUnit.SECONDS);
                    }
                    //雪崩
                    Random random = new Random();
                    int i = random.nextInt(300);

                    redisTemplate.opsForValue().set("rediscateloglist",s,300+i,TimeUnit.SECONDS);
                    return cateloglist;
                }
            }finally {
                writeLock.unlock();
            }

        }

    }


    @Override
    public HashMap<String, List<Catalog2Vo>> cateloglist() {

        HashMap<String, List<Catalog2Vo>> hashmap = new HashMap<>();
        System.out.println("金数据库");
        QueryWrapper<CategoryEntity> queryWrapper = new QueryWrapper<>();
        //parentCid
        queryWrapper.eq("parent_cid",0L);
        List<CategoryEntity> entityList = baseMapper.selectList(queryWrapper);
        for (CategoryEntity categoryEntity : entityList) {
            //根据一级菜单，获取二级菜单
            hashmap.put(categoryEntity.getCatId().toString(),getcateloglist2(categoryEntity.getCatId()));
        }
        return hashmap;
    }

    //普通缓存 包括（穿透和雪崩）
    @Override
    public HashMap<String, List<Catalog2Vo>> cateloglistCache() {
        //进入redis查询
        String cateloglist = redisTemplate.opsForValue().get("rediscateloglist");
        if (cateloglist != null) {
            //JSON.parseObject:转换成object对象
            HashMap<String, List<Catalog2Vo>> listHashMap = JSON.parseObject(cateloglist, new TypeReference<HashMap<String, List<Catalog2Vo>>>() {
            });
            System.out.println("缓存命中");
            return listHashMap;
        }else{
            //redis中不存在，就去数据查询
            HashMap<String, List<Catalog2Vo>> catelist = cateloglist();
            //json.parseobject:是转换为object对象 而 json.tojsonstring是转化为json类型
            String s = JSON.toJSONString(catelist);
            //判断 如果s在数据查询到的值是空值
            if(s==null || StringUtils.isEmpty(s)){
                //缓存穿透  短暂缓存 null 值  只能是短暂  因为过段时间要是有数据的话要及时去添加进redis
                redisTemplate.opsForValue().set("rediscateloglist",s,30, TimeUnit.SECONDS);
            }
            //加入缓存时间 处理雪崩问题
            Random random = new Random();
            int i = random.nextInt(300);
            redisTemplate.opsForValue().set("rediscateloglist",s,300+i,TimeUnit.SECONDS);
            return catelist;
        }
    }

    public List<Catalog2Vo> getcateloglist2(Long catid){
        QueryWrapper<CategoryEntity> queryWrapper = new QueryWrapper<>();
        //parentCid
        queryWrapper.eq("parent_cid",catid);
        List<CategoryEntity> entityList = baseMapper.selectList(queryWrapper);
        List<Catalog2Vo> catalog2VoList = entityList.stream().map(m -> {
            Catalog2Vo catalog2Vo = new Catalog2Vo();
            catalog2Vo.setCatalog1Id(catid.toString());
            catalog2Vo.setId(m.getCatId().toString());
            catalog2Vo.setName(m.getName());
            catalog2Vo.setCatalog3list(getcateloglist3(m.getCatId()));
            return catalog2Vo;
        }).collect(Collectors.toList());
        return catalog2VoList;
    }
    public List<Catalog3Vo> getcateloglist3(Long catid){
        QueryWrapper<CategoryEntity> queryWrapper = new QueryWrapper<>();
        //parentCid
        queryWrapper.eq("parent_cid",catid);
        List<CategoryEntity> entityList = baseMapper.selectList(queryWrapper);
        List<Catalog3Vo> collect = entityList.stream().map(m -> {
            Catalog3Vo catalog3Vo = new Catalog3Vo();
            catalog3Vo.setCatalog2Id(catid.toString());
            catalog3Vo.setId(m.getCatId().toString());
            catalog3Vo.setName(m.getName());
            return catalog3Vo;
        }).collect(Collectors.toList());
        return collect;
    }

}